﻿//
// Copyright (C) Ashish Kaila 2011
//

using System.Collections.ObjectModel;
using System.IO;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using Flipbook.Commands;
using Microsoft.Win32;
using MixModes.Synergy.VisualFramework.ViewModels;

namespace Flipbook.ViewModels
{
    /// <summary>
    /// Application view model
    /// </summary>
    public class ApplicationViewModel : ViewModelBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ApplicationViewModel"/> class.
        /// </summary>
        public ApplicationViewModel()
        {
            BackCanvasViewModel.Canvas.Visibility = System.Windows.Visibility.Hidden;

            AddCanvasCommand = new DispatchCommand(arg => AddCanvas());
            RemoveCanvasCommand = new DispatchCommand(arg => RemoveCanvas());
            CloneCanvasCommand = new DispatchCommand(arg => CloneCanvas(), arg => ActiveCanvasViewModel != null);
            NewCommand = new DispatchCommand(arg => New());
            SaveCommand = new DispatchCommand(arg => Save());
            OpenCommand = new DispatchCommand(arg => Open());
            RewindCommand = new DispatchCommand(arg => ActiveCanvasViewModel = CanvasViewModels[0], arg=>CanvasViewModels.Count > 0);
            ForwardCommand = new DispatchCommand(arg => ActiveCanvasViewModel = CanvasViewModels[CanvasViewModels.Count - 1], arg => CanvasViewModels.Count > 0);
            PlayCommand = new DispatchCommand(arg => AnimateFlipbook(), arg => CanvasViewModels.Count > 0);

            PreviewCommand = new DispatchCommand(arg => PreviewAnimation(), arg => ActiveCanvasViewModel != null);

            CanvasViewModels = new ObservableCollection<CanvasViewModel>();
            DesignerProperties = new DesignerPropertiesViewModel();
 
            // Initialize view
            New();
        }

        /// <summary>
        /// Gets the designer properties.
        /// </summary>
        public DesignerPropertiesViewModel DesignerProperties
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the active canvas view model.
        /// </summary>
        public CanvasViewModel ActiveCanvasViewModel
        {
            get
            {
                return _activeCanvasViewModel;
            }
            set
            {
                IsTraceEnabled = false;
                IsFlipEnabled = false;
                _activeCanvasViewModel = value;
                RaisePropertyChanged(arg=>ActiveCanvasViewModel);
            }
        }            

        /// <summary>
        /// Gets the back canvas view model.
        /// </summary>
        public CanvasViewModel BackCanvasViewModel
        {
            get
            {
                return _backCanvasViewModel;
            }
            set
            {
                _backCanvasViewModel = value;
                RaisePropertyChanged(arg => BackCanvasViewModel);
            }
        }
        
        /// <summary>
        /// Gets the add canvas command.
        /// </summary>
        public ICommand AddCanvasCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the new command.
        /// </summary>
        public ICommand NewCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the save canvas command.
        /// </summary>
        public ICommand SaveCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the open command.
        /// </summary>
        public ICommand OpenCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the remove canvas command.
        /// </summary>
        public ICommand RemoveCanvasCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the clone canvas command.
        /// </summary>
        public ICommand CloneCanvasCommand
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the rewind command.
        /// </summary>
        public ICommand RewindCommand
        {
            get;
            private set;             
        }

        /// <summary>
        /// Gets the play command.
        /// </summary>
        public ICommand PlayCommand
        {
            get;
            private set;
        }

        public ICommand PreviewCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the forward command.
        /// </summary>
        public ICommand ForwardCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether trace is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if trace is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsTraceEnabled
        {
            get
            {
                return _isTraceEnabled;
            }
            set
            {
                if (_isTraceEnabled == value)
                {
                    return;
                }

                _isTraceEnabled = value;

                if (_isTraceEnabled)
                {
                    ActiveCanvasViewModel.Canvas.Strokes.StrokesChanged += TraceStroke;
                }
                else
                {
                    ActiveCanvasViewModel.Canvas.Strokes.StrokesChanged -= TraceStroke;
                }

                RaisePropertyChanged(x=>IsTraceEnabled);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether flip is enabled.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if flip is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsFlipEnabled
        {
            get
            {
                return _isFlipEnabled;
            }
            set
            {
                if (_isFlipEnabled == value)
                {
                    return;
                }

                _isFlipEnabled = value;

                if (_isFlipEnabled)
                {
                    int index = CanvasViewModels.IndexOf(ActiveCanvasViewModel);
                    if (index > 0)
                    {
                        ActiveCanvasViewModel.Canvas.Opacity = 0.75;
                        BackCanvasViewModel.Canvas.Opacity = 0.25;
                        CloneCanvas(index - 1);
                    }
                }
                else
                {
                    ActiveCanvasViewModel.Canvas.Opacity = 1.0;
                    BackCanvasViewModel.Canvas.Visibility = System.Windows.Visibility.Hidden;
                }

                RaisePropertyChanged(x => IsFlipEnabled);
            }
        }

        /// <summary>
        /// Gets or sets the frame rate.
        /// </summary>
        /// <value>
        /// The frame rate.
        /// </value>
        public int FrameRate
        {
            get { return _frameRate; }
            set
            {
                _frameRate = value;
                RaisePropertyChanged(x=>FrameRate);
            }
        }

        /// <summary>
        /// Gets the canvas view models.
        /// </summary>
        public ObservableCollection<CanvasViewModel> CanvasViewModels
        {
            get; private set;
        }

        /// <summary>
        /// Adds the canvas.
        /// </summary>
        private void AddCanvas()
        {
            int activeIndex = CanvasViewModels.IndexOf(_activeCanvasViewModel);
            AddCanvas(new CanvasViewModel(), activeIndex+1);
        }
        
        /// <summary>
        /// Adds the canvas.
        /// </summary>
        /// <param name="canvasViewModel">The canvas view model.</param>
        /// <param name="index">The index.</param>
        private void AddCanvas(CanvasViewModel canvasViewModel, int index)
        {
            AddCanvas(canvasViewModel, index, true);
        }

        /// <summary>
        /// Adds the canvas.
        /// </summary>
        /// <param name="canvasViewModel">The canvas view model.</param>
        /// <param name="index">The index.</param>
        /// <param name="setNewCanvasAsActive">if set to <c>true</c> sets the new canvas as active canvas</param>
        private void AddCanvas(CanvasViewModel canvasViewModel, int index, bool setNewCanvasAsActive)
        {
            CanvasViewModels.Insert(index, canvasViewModel);

            if (setNewCanvasAsActive)
            {
                ActiveCanvasViewModel = canvasViewModel;
            }
        }

        /// <summary>
        /// Removes the canvas.
        /// </summary>
        private void RemoveCanvas()
        {
            if (ActiveCanvasViewModel != null)
            {
                int activeIndex = CanvasViewModels.IndexOf(ActiveCanvasViewModel);
                CanvasViewModel newActiveCanvasViewModel = activeIndex > 0 ? CanvasViewModels[activeIndex - 1] :
                                                           CanvasViewModels.Count > 1 ? CanvasViewModels[activeIndex + 1] : null;
                CanvasViewModels.Remove(ActiveCanvasViewModel);

                if (newActiveCanvasViewModel != null)
                {
                    ActiveCanvasViewModel = newActiveCanvasViewModel;
                }
            }

            if (CanvasViewModels.Count == 0)
            {
                AddCanvas();                
            }
        }

        /// <summary>
        /// Clones the canvas.
        /// </summary>
        private void CloneCanvas()
        {
            if (ActiveCanvasViewModel != null)
            {                
                CanvasViewModel newCanvasViewModel = new CanvasViewModel();
                StrokeCollection stokeCollection = ActiveCanvasViewModel.Canvas.Strokes;                
                foreach (Stroke stroke in stokeCollection)
                {
                    Stroke newStroke = stroke.Clone();
                    newCanvasViewModel.Canvas.Strokes.Add(newStroke);
                }

                int index = CanvasViewModels.IndexOf(ActiveCanvasViewModel);
                AddCanvas(newCanvasViewModel, index+1);
            }
        }

        /// <summary>
        /// Clones the canvas at a specific index and sets it to the back canvas - used for flipping
        /// </summary>
        /// <param name="index"></param>
        private void CloneCanvas(int index)
        {
            if (index >= 0)
            {
                CanvasViewModel newCanvasViewModel = new CanvasViewModel();
                StrokeCollection strokeCollection = CanvasViewModels[index].Canvas.Strokes;
                foreach (Stroke stroke in strokeCollection)
                {
                    Stroke newStroke = stroke.Clone();
                    newCanvasViewModel.Canvas.Strokes.Add(newStroke);
                }
                BackCanvasViewModel = newCanvasViewModel;
                BackCanvasViewModel.Canvas.Visibility = System.Windows.Visibility.Visible;
            }
        }

        /// <summary>
        /// Creates a new flipbook
        /// </summary>
        private void New()
        {
            FrameRate = DefaultFrameRate;
            CanvasViewModels.Clear();
            AddCanvas();
        }

        /// <summary>
        /// Saves existing flipbook
        /// </summary>
        private void Save()
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog();
            saveFileDialog.Filter = "Flipbook Files (*.flp)|*.flp";

            bool? dialogResult = saveFileDialog.ShowDialog();

            if ((dialogResult.HasValue) && (dialogResult.Value))
            {
                using (Stream stream = saveFileDialog.OpenFile())
                {
                    foreach (CanvasViewModel canvasViewModel in CanvasViewModels)
                    {
                        canvasViewModel.Canvas.Strokes.Save(stream, true);
                    }
                }
            }                       
        }

        /// <summary>
        /// Opens an existing flipbook
        /// </summary>
        private void Open()
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            openFileDialog.Filter = "Flipbook Files (*.flp)|*.flp";

            bool? dialogResult = openFileDialog.ShowDialog();

            if ((dialogResult.HasValue) && (dialogResult.Value))
            {
                using (Stream stream = openFileDialog.OpenFile())
                {
                    CanvasViewModels.Clear();
                    
                    while (stream.Position < stream.Length)
                    {                        
                        CanvasViewModels.Add(new CanvasViewModel(new InkCanvas{Strokes=new StrokeCollection(stream)}));
                    }

                    if (CanvasViewModels.Count > 0)
                    {
                        ActiveCanvasViewModel = CanvasViewModels[0];
                    }                    
                }
            }
        }        

        /// <summary>
        /// Called when strokes are changed in order to trace the strokes
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Ink.StrokeCollectionChangedEventArgs"/> instance containing the event data.</param>
        private void TraceStroke(object sender, StrokeCollectionChangedEventArgs e)
        {
            int activeCanvasIndex = CanvasViewModels.IndexOf(ActiveCanvasViewModel);

            if (CanvasViewModels.Count == (activeCanvasIndex + 1))
            {
                AddCanvas(new CanvasViewModel(), CanvasViewModels.Count, false);
            }

            CanvasViewModel nextCanvas = CanvasViewModels[activeCanvasIndex + 1];

            foreach (Stroke stroke in e.Added)
            {
                Stroke clonedStroke = stroke.Clone();
                clonedStroke.DrawingAttributes.Color = Color.FromArgb(25, 0, 0, 0);
                nextCanvas.Canvas.Strokes.Add(clonedStroke);
            }
        }

        /// <summary>
        /// Animates the flipbook.
        /// </summary>
        private void AnimateFlipbook()
        {
            AnimationWindow animationWindow = new AnimationWindow(CanvasViewModels, FrameRate);
            animationWindow.Show();
        }

        private void PreviewAnimation()
        {
            int activeCanvasIndex = CanvasViewModels.IndexOf(ActiveCanvasViewModel);
            if (activeCanvasIndex == 0) activeCanvasIndex = 1;
            AnimationWindow animationWindow = new AnimationWindow(CanvasViewModels, FrameRate/6, activeCanvasIndex-1);
            animationWindow.Show();
        }

        // Private members
        private const int DefaultFrameRate = 33;
        private CanvasViewModel _activeCanvasViewModel;
        private CanvasViewModel _backCanvasViewModel = new CanvasViewModel();
        private bool _isTraceEnabled;
        private bool _isFlipEnabled;
        private int _frameRate;
    }
}
